﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.Logging;
using StyleMVVM.Utilities;
using StyleMVVM.Data;
#if !DOT_NET
using Windows.Foundation;
using Windows.Foundation.Metadata;
#endif
#if NETFX_CORE
using Windows.UI.Xaml;

#else
using System.Windows;

#endif

namespace StyleMVVM.View.Regions
{
	/// <summary>
	/// This is the base implementation of IRegion.
	/// </summary>
	public sealed class BaseRegion : IRegion
	{
		private static readonly string supplemental = typeof(BaseRegion).FullName;
		private readonly IRegionAdapter adapter;
		private DependencyObject control;
		private readonly List<Tuple<string, object>> navigationStack;
		private int navigationIndex;
		private IEnumerable<IViewBinder> viewBinders;

		public BaseRegion()
		{
			navigationStack = new List<Tuple<string, object>>();
		}

		public BaseRegion(string name, string context, IRegionAdapter adapter, IEnumerable<IViewBinder> viewBinders)
		{
			Name = name;
			Context = context;

			this.viewBinders = viewBinders;
			this.adapter = adapter;
			navigationStack = new List<Tuple<string, object>>();
		}

		#region CurrentRegion

		public static IList<IRegion> GetCurrentRegions(DependencyObject obj)
		{
			return (IList<IRegion>)obj.GetValue(InteralCurrentRegionsProperty);
		}

		public static void SetCurrentRegion(DependencyObject obj, IList<IRegion> value)
		{
			obj.SetValue(InteralCurrentRegionsProperty, value);
		}

		public static DependencyProperty CurrentRegionsProperty
		{
			get { return InteralCurrentRegionsProperty; }
		}

		// Using a DependencyProperty as the backing store for CurrentRegion.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InteralCurrentRegionsProperty =
			DependencyProperty.RegisterAttached("CurrentRegions",
			                                    typeof(IList<IRegion>),
			                                    typeof(BaseRegion),
			                                    new PropertyMetadata(null));

		#endregion

		#region ExportName Property

		public static string GetExportName(DependencyObject obj)
		{
			return (string)obj.GetValue(InternalExportNameProperty);
		}

		public static void SetExportName(DependencyObject obj, string value)
		{
			obj.SetValue(InternalExportNameProperty, value);
		}

		public static DependencyProperty ExportNameProperty
		{
			get { return InternalExportNameProperty; }
		}

		// Using a DependencyProperty as the backing store for ExportName.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalExportNameProperty =
			DependencyProperty.RegisterAttached("ExportName",
			                                    typeof(string),
			                                    typeof(BaseRegion),
			                                    new PropertyMetadata(null));

		#endregion

		#region IRegion

		public IRegionManager RegionManager { get; set; }

		public void Hook(object regionObject)
		{
			control = regionObject as DependencyObject;

			DependencyObject dependencyObject = regionObject as DependencyObject;

			if (dependencyObject != null)
			{
				IList<IRegion> regionsList = GetCurrentRegions(dependencyObject);

				if (regionsList == null)
				{
					regionsList = new List<IRegion>();
				}

				regionsList.Add(this);

				SetCurrentRegion(dependencyObject, regionsList);

				RegionManager.Regions.TryAdd(FullName, this);
			}
			else
			{
				Logger.Error("Cannot hook to region object that is not dependency object");
			}
		}

		public void Unhook()
		{
			object regionObject;
			DependencyObject dependencyObject = Control as DependencyObject;

			if (dependencyObject != null)
			{
				IList<IRegion> regionsList = GetCurrentRegions(dependencyObject);

				if (regionsList != null)
				{
					regionsList.Remove(this);

					if (regionsList.Count == 0)
					{
						SetCurrentRegion(dependencyObject, null);
					}
				}

				RegionManager.Regions.Remove(FullName);
			}
			else
			{
				Logger.Error("Cannot hook to region object that is not dependency object", supplemental);
			}
		}

		public string Name { get; protected set; }

		public string Context { get; protected set; }

		public string FullName
		{
			get
			{
				string returnValue = Name;

				if (!string.IsNullOrEmpty(Context))
				{
					returnValue = string.Concat(Name, '|', Context);
				}

				return returnValue;
			}
		}

		public string Home { get; set; }

		public bool CanGoForward
		{
			get { return (navigationStack.Count - 1) - navigationIndex > 0; }
		}

		public bool CanGoBack
		{
			get { return navigationIndex > 0; }
		}

		public bool IsContainerMaster { get; set; }

		public IEnumerable<object> GetCurrentExports()
		{
			return adapter.GetCurrentExports(this, Control);
		}

#if NETFX_CORE
		public IAsyncOperation<bool> GoForward()
		{
			return InternalGoForward().AsAsyncOperation();
		}

		public IAsyncOperation<bool> GoBack()
		{
			return InternalGoBack().AsAsyncOperation();
		}

		public IAsyncOperation<bool> GoHome()
		{
			return InternalGoHome().AsAsyncOperation();
		}

		public IAsyncOperation<bool> Activate(string exportName)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Activate(this, control, exportName);
				}
			}

			return AsyncHelper.ReturnAsyncOp(false);
		}

		public IAsyncOperation<bool> Navigate(string exportName)
		{
			return Navigate(exportName, null);
		}

		public IAsyncOperation<bool> Navigate(string exportName, object navigationParameter)
		{
			return InternalNavigate(exportName, navigationParameter).AsAsyncOperation();
		}

		[DefaultOverload]
		public IAsyncOperation<bool> Add(string exportName)
		{
			return Add(exportName, null);
		}

		[DefaultOverload]
		public IAsyncOperation<bool> Add(string exportName, object navigationParameter)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Add(this, control, exportName, navigationParameter);
				}
			}

			return AsyncHelper.ReturnAsyncOp(false);
		}

		public IAsyncOperation<bool> Add(object export)
		{
			return Add(export, null);
		}

		public IAsyncOperation<bool> Add(object export, object navigationParameter)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Add(this, control, export, navigationParameter);
				}
			}

			return AsyncHelper.ReturnAsyncOp(false);
		}

		public IAsyncOperation<bool> Deactivate(string exportName)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Deactivate(this, control, exportName);
				}
			}

			return AsyncHelper.ReturnAsyncOp(false);
		}

		[DefaultOverload]
		public IAsyncOperation<bool> Remove(string exportName)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Remove(this, control, exportName);
				}
			}

			return AsyncHelper.ReturnAsyncOp(false);
		}

		public IAsyncOperation<bool> Remove(object export)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Remove(this, control, export);
				}
			}

			return AsyncHelper.ReturnAsyncOp(false);
		}
#else
		public Task<bool> GoForward()
		{
			return InternalGoForward();
		}

		public Task<bool> GoBack()
		{
			return InternalGoBack();
		}

		public Task<bool> GoHome()
		{
			return InternalGoHome();
		}

		public Task<bool> Activate(string exportName)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Activate(this, control, exportName);
				}
			}

			return TaskHelper.ReturnTask(false);
		}

		public Task<bool> Navigate(string exportName)
		{
			return InternalNavigate(exportName, null);
		}

		public Task<bool> Navigate(string exportName, object navigationParameter)
		{
			return InternalNavigate(exportName, navigationParameter);
		}

		[DefaultOverload]
		public Task<bool> Add(string exportName)
		{
			return Add(exportName, null);
		}

		[DefaultOverload]
		public Task<bool> Add(string exportName, object navigationParameter)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Add(this, control, exportName, navigationParameter);
				}
			}

			return TaskHelper.ReturnTask(false);
		}

		public Task<bool> Add(object export)
		{
			return Add(export, null);
		}

		public Task<bool> Add(object export, object navigationParameter)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Add(this, control, export, navigationParameter);
				}
			}

			return TaskHelper.ReturnTask(false);
		}

		public Task<bool> Deactivate(string exportName)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Deactivate(this, control, exportName);
				}
			}

			return TaskHelper.ReturnTask(false);
		}

		[DefaultOverload]
		public Task<bool> Remove(string exportName)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Remove(this, control, exportName);
				}
			}

			return TaskHelper.ReturnTask(false);
		}

		public Task<bool> Remove(object export)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return adapter.Remove(this, control, export);
				}
			}

			return TaskHelper.ReturnTask(false);
		}
#endif

		#endregion

		#region Private / Protected Members

		internal async Task<bool> InternalGoForward()
		{
			if (CanGoForward)
			{
				int newNavigationIndex = navigationIndex + 1;
				Tuple<string, object> navigateCommand = navigationStack[newNavigationIndex];

				if (navigateCommand.Item2 != null)
				{
					if (await NavigateWithoutChangingStack(navigateCommand.Item1, navigateCommand.Item2))
					{
						navigationIndex = newNavigationIndex;

						OnPropertyChanged("CanGoForward");
						OnPropertyChanged("CanGoBack");

						return true;
					}
				}
				else if (await NavigateWithoutChangingStack(navigateCommand.Item1))
				{
					navigationIndex = newNavigationIndex;

					OnPropertyChanged("CanGoForward");
					OnPropertyChanged("CanGoBack");

					return true;
				}
			}

			return false;
		}

		internal async Task<bool> InternalGoBack()
		{
			if (CanGoBack)
			{
				int newNavigationIndex = navigationIndex - 1;
				Tuple<string, object> navigateCommand = navigationStack[newNavigationIndex];

				if (navigateCommand.Item2 != null)
				{
					if (await NavigateWithoutChangingStack(navigateCommand.Item1, navigateCommand.Item2))
					{
						navigationIndex = newNavigationIndex;

						OnPropertyChanged("CanGoForward");
						OnPropertyChanged("CanGoBack");

						return true;
					}
				}
				else if (await NavigateWithoutChangingStack(navigateCommand.Item1))
				{
					navigationIndex = newNavigationIndex;

					OnPropertyChanged("CanGoForward");
					OnPropertyChanged("CanGoBack");

					return true;
				}
			}

			return false;
		}

		internal async Task<bool> InternalGoHome()
		{
			if (!string.IsNullOrEmpty(Home) && await NavigateWithoutChangingStack(Home))
			{
				navigationStack.Clear();

				navigationStack.Add(new Tuple<string, object>(Home, null));
				navigationIndex = 0;

				OnPropertyChanged("CanGoForward");
				OnPropertyChanged("CanGoBack");

				return true;
			}

			return false;
		}

		internal async Task<bool> InternalNavigate(string exportName, object navigationParameter)
		{
			bool returnValue = await NavigateWithoutChangingStack(exportName, navigationParameter);

			if (returnValue)
			{
				if ((navigationStack.Count - 1) > navigationIndex)
				{
					navigationStack.RemoveRange(navigationIndex + 1,
					                            (navigationStack.Count - 1) - navigationIndex);
				}

				navigationStack.Add(new Tuple<string, object>(exportName, navigationParameter));

				navigationIndex = navigationStack.Count - 1;

				OnPropertyChanged("CanGoForward");
				OnPropertyChanged("CanGoBack");
			}

			return returnValue;
		}

		internal async Task<bool> NavigateWithoutChangingStack(string exportName, object navigationParameter = null)
		{
			if (adapter != null)
			{
				object control = Control;

				if (control != null)
				{
					return await adapter.Navigate(this, control, exportName, navigationParameter);
				}
			}

			return false;
		}

		protected object Control
		{
			get { return control; }
		}

		#endregion

		public event PropertyChangedEventHandler PropertyChanged;

		private void OnPropertyChanged(string propertyName)
		{
			PropertyChangedEventHandler handler = PropertyChanged;
			if (handler != null)
			{
				handler(this, new PropertyChangedEventArgs(propertyName));
			}
		}
	}
}